Merge pull request #156 from rerdavies/1.2.34

1.2.34 Merge.
This commit is contained in:
Robin Davies
2024-08-10 14:09:38 -04:00
committed by GitHub
18 changed files with 445 additions and 129 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.33 VERSION 1.2.34
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.31beta1") set (DISPLAY_VERSION "v1.2.34beta2")
set (CMAKE_INSTALL_PREFIX "/usr/") set (CMAKE_INSTALL_PREFIX "/usr/")
+2 -2
View File
@@ -1,12 +1,12 @@
<img src='docs/GithubBanner.png' width="100%" /><br/> <img src='docs/GithubBanner.png' width="100%" /><br/>
Download: <a href='https://rerdavies.github.io/pipedal/download.html'>v1.2.33 Beta1</a> Download: <a href='https://rerdavies.github.io/pipedal/download.html'>v1.2.34 Beta1</a>
Website: [https://rerdavies.github.io/pipedal](https://rerdavies.github.io/pipedal). Website: [https://rerdavies.github.io/pipedal](https://rerdavies.github.io/pipedal).
> NEW version 1.2.33 beta1 release, providing support for Raspberry Pi OS Bookworm. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details. > NEW version 1.2.34 beta2 release, providing support for Raspberry Pi OS Bookworm. 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 provides stable super-low-latency audio via external USB audio devices, or internal Raspberry Pi audio hats.
+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.33/pipedal_1.2.33_arm64.deb">Raspberry Pi OS Bookworm (64-bit) v1.2.33 Beta1</a> - <a href="https://github.com/rerdavies/pipedal/releases/download/v1.2.34/pipedal_1.2.34_arm64.deb">Raspberry Pi OS Bookworm (64-bit) v1.2.34 Beta2</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 Buster (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 Buster (64-bit) v1.1.31</a>
v1.2.33 Beta1 is does not currently support Ubuntu 21.04, or older versions of Raspberry Pi OS. v1.2.34 Beta1 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.33_arm64.deb sudo apt-get install ./pipedal_1.2.34_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.
+3 -2
View File
@@ -29,13 +29,14 @@ import IControlViewFactory from './IControlViewFactory';
import { GxTunerViewFactory } from './GxTunerView'; import { GxTunerViewFactory } from './GxTunerView';
import ToobPowerstage2ViewFactory from './ToobPowerStage2View'; import ToobPowerstage2ViewFactory from './ToobPowerStage2View';
import ToobSpectrumAnalyzerViewFactory from './ToobSpectrumAnalyzerView'; import ToobSpectrumAnalyzerViewFactory from './ToobSpectrumAnalyzerView';
// import ToobMLViewFactory from './ToobMLView'; import ToobMLViewFactory from './ToobMLView';
let pluginFactories: IControlViewFactory[] = [ let pluginFactories: IControlViewFactory[] = [
new GxTunerViewFactory(), new GxTunerViewFactory(),
new ToobPowerstage2ViewFactory(), new ToobPowerstage2ViewFactory(),
new ToobSpectrumAnalyzerViewFactory(), new ToobSpectrumAnalyzerViewFactory(),
//new ToobMLViewFactory() new ToobMLViewFactory()
]; ];
+145 -3
View File
@@ -506,7 +506,7 @@ export class UiControl implements Deserializable<UiControl> {
this.controlType = ControlType.Vu; this.controlType = ControlType.Vu;
} }
} }
else if (this.isValidEnumeration()) if (this.isValidEnumeration())
{ {
this.controlType = ControlType.Select; this.controlType = ControlType.Select;
if (this.scale_points.length === 2) if (this.scale_points.length === 2)
@@ -520,7 +520,10 @@ export class UiControl implements Deserializable<UiControl> {
} }
} }
return this; return this;
}
applyProperties(properties: Partial<UiControl>): UiControl
{
return {...this,...properties};
} }
private hasScalePoint(value: number): boolean { private hasScalePoint(value: number): boolean {
for (let scale_point of this.scale_points) for (let scale_point of this.scale_points)
@@ -726,7 +729,7 @@ export class UiControl implements Deserializable<UiControl> {
text += "s"; text += "s";
break; break;
// Midinote: not handled. // Midinote: not handled.
// semitone12TET not handled. // semitone12TET not handled.
@@ -759,6 +762,7 @@ export class UiControl implements Deserializable<UiControl> {
} }
export class UiPlugin implements Deserializable<UiPlugin> { export class UiPlugin implements Deserializable<UiPlugin> {
deserialize(input: any): UiPlugin deserialize(input: any): UiPlugin
{ {
@@ -802,6 +806,11 @@ export class UiPlugin implements Deserializable<UiPlugin> {
} }
return result; return result;
} }
isSplit(): boolean {
return this.uri === "uri://two-play/pipedal/pedalboard#Split";
}
getControl(key: string): UiControl | undefined { getControl(key: string): UiControl | undefined {
for (let i = 0; i < this.controls.length; ++i) for (let i = 0; i < this.controls.length; ++i)
{ {
@@ -860,3 +869,136 @@ export class UiPlugin implements Deserializable<UiPlugin> {
is_vst3 : boolean = false; is_vst3 : boolean = false;
} }
export function makeSplitUiPlugin(): UiPlugin
{
return new UiPlugin().deserialize({
uri: "uri://two-play/pipedal/pedalboard#Split",
name: "Split",
brand: "",
label: "",
plugin_type: PluginType.SplitA,
plugin_display_type: "Split",
author_name: "",
author_homepage: "",
audio_inputs: 1,
audio_outputs: 1,
has_midi_input: 0,
has_midi_output: 0,
description: "",
controls: [
new UiControl().applyProperties({
symbol: "splitType",
name: "Type",
index: 0,
is_input: true,
min_value: 0.0,
max_value: 2.0,
enumeration_property: true,
scale_points: [
new ScalePoint().deserialize({value: 0, label: "A/B"}),
new ScalePoint().deserialize({value: 1, label: "mix"}),
new ScalePoint().deserialize({value: 1, label: "L/R"}),
],
is_bypass: false,
is_program_controller: false,
custom_units: "",
connection_optional: false,
}) ,
new UiControl().applyProperties({
symbol: "select",
name: "Select",
index: 1,
is_input: true,
min_value: 0.0,
max_value: 1.0,
enumeration_property: true,
scale_points: [
new ScalePoint().deserialize({value: 0, label: "A"}),
new ScalePoint().deserialize({value: 1, label: "B"}),
],
is_bypass: false,
is_program_controller: false,
custom_units: "",
connection_optional: false,
}) ,
new UiControl().applyProperties({
symbol: "mix",
name: "Mix",
index: 2,
is_input: true,
min_value: -1.0,
max_value: 1.0,
is_bypass: false,
is_program_controller: false,
custom_units: "",
connection_optional: false,
}) ,
new UiControl().applyProperties({
symbol: "panL",
name: "Pan Top",
index: 3,
is_input: true,
min_value: -1.0,
max_value: 1.0,
is_bypass: false,
is_program_controller: false,
custom_units: "",
connection_optional: false,
}) ,
new UiControl().applyProperties({
symbol: "volL",
name: "Vol Top",
index: 4,
is_input: true,
min_value: -60.0,
max_value: 12.0,
is_bypass: false,
is_program_controller: false,
custom_units: "",
connection_optional: false,
}) ,
new UiControl().applyProperties({
symbol: "panR",
name: "Pan Bottom",
index: 5,
is_input: true,
min_value: -1.0,
max_value: 1.0,
is_bypass: false,
is_program_controller: false,
custom_units: "",
connection_optional: false,
}) ,
new UiControl().applyProperties({
symbol: "volR",
name: "Vol Bottom",
index: 6,
is_input: true,
min_value: -60.0,
max_value: 12.0,
is_bypass: false,
is_program_controller: false,
custom_units: "",
connection_optional: false,
})
],
port_groups: [],
fileProperties: [],
frequencyPlots: [],
is_vst3 : false,
}
);
}
+8 -3
View File
@@ -32,6 +32,7 @@ import MicNoneOutlinedIcon from '@mui/icons-material/MicNoneOutlined';
import MicOutlinedIcon from '@mui/icons-material/MicOutlined'; import MicOutlinedIcon from '@mui/icons-material/MicOutlined';
import IconButton from '@mui/material/IconButton'; import IconButton from '@mui/material/IconButton';
import NumericInput from './NumericInput'; import NumericInput from './NumericInput';
import { UiPlugin } from './Lv2Plugin';
@@ -45,6 +46,7 @@ interface MidiBindingViewProps extends WithStyles<typeof styles> {
instanceId: number; instanceId: number;
listen: boolean; listen: boolean;
midiBinding: MidiBinding; midiBinding: MidiBinding;
uiPlugin: UiPlugin;
onChange: (instanceId: number, newBinding: MidiBinding) => void; onChange: (instanceId: number, newBinding: MidiBinding) => void;
onListen: (instanceId: number, key: string, listenForControl: boolean) => void; onListen: (instanceId: number, key: string, listenForControl: boolean) => void;
} }
@@ -139,8 +141,7 @@ const MidiBindingView =
render() { render() {
let classes = this.props.classes; let classes = this.props.classes;
let midiBinding = this.props.midiBinding; let midiBinding = this.props.midiBinding;
let pedalboardItem = this.model.pedalboard.get().getItem(this.props.instanceId); let uiPlugin = this.props.uiPlugin;
let uiPlugin = this.model.getUiPlugin(pedalboardItem.uri);
if (!uiPlugin) { if (!uiPlugin) {
return (<div />); return (<div />);
} }
@@ -258,7 +259,11 @@ const MidiBindingView =
value={midiBinding.switchControlType} value={midiBinding.switchControlType}
> >
<MenuItem value={MidiBinding.LATCH_CONTROL_TYPE}>Toggle</MenuItem> <MenuItem value={MidiBinding.LATCH_CONTROL_TYPE}>Toggle</MenuItem>
<MenuItem value={MidiBinding.MOMENTARY_CONTROL_TYPE}>Momentary</MenuItem> <MenuItem value={MidiBinding.MOMENTARY_CONTROL_TYPE}>{
(midiBinding.bindingType === MidiBinding.BINDING_TYPE_NOTE) ?
"Note on/off"
: "Control value"
}</MenuItem>
</Select> </Select>
</div> </div>
)) ))
+70 -24
View File
@@ -33,6 +33,7 @@ import IconButton from '@mui/material/IconButton';
import MidiBinding from './MidiBinding'; import MidiBinding from './MidiBinding';
import MidiBindingView from './MidiBindingView'; import MidiBindingView from './MidiBindingView';
import Snackbar from '@mui/material/Snackbar'; import Snackbar from '@mui/material/Snackbar';
import { UiPlugin,makeSplitUiPlugin } from './Lv2Plugin';
const styles = (theme: Theme) => createStyles({ const styles = (theme: Theme) => createStyles({
dialogAppBar: { dialogAppBar: {
@@ -62,6 +63,10 @@ const styles = (theme: Theme) => createStyles({
}, },
}); });
function not_null<T>(value: T | null) {
if (!value) throw Error("Unexpected null value");
return value;
}
export interface MidiBindingDialogProps extends WithStyles<typeof styles> { export interface MidiBindingDialogProps extends WithStyles<typeof styles> {
open: boolean, open: boolean,
@@ -189,7 +194,43 @@ export const MidiBindingDialog =
let item = v.value; let item = v.value;
let plugin = this.model.getUiPlugin(item.uri); let isSplit = item.uri === "uri://two-play/pipedal/pedalboard#Split";
let plugin : UiPlugin | null = this.model.getUiPlugin(item.uri);
if (plugin === null && isSplit)
{
plugin = makeSplitUiPlugin();
let splitType = item.getControlValue("splitType");
not_null(plugin.getControl("splitType")).not_on_gui = true;
switch (splitType)
{
case 0: // A/B
not_null(plugin.getControl("select")).not_on_gui = false;
not_null(plugin.getControl("mix")).not_on_gui = true;
not_null(plugin.getControl("volL")).not_on_gui = true;
not_null(plugin.getControl("panL")).not_on_gui = true;
not_null(plugin.getControl("volR")).not_on_gui = true;
not_null(plugin.getControl("panR")).not_on_gui = true;
break;
case 1: //mixer
not_null(plugin.getControl("select")).not_on_gui = true;
not_null(plugin.getControl("mix")).not_on_gui = false;
not_null(plugin.getControl("volL")).not_on_gui = true;
not_null(plugin.getControl("panL")).not_on_gui = true;
not_null(plugin.getControl("volR")).not_on_gui = true;
not_null(plugin.getControl("panR")).not_on_gui = true;
break;
case 2: // L/R
not_null(plugin.getControl("select")).not_on_gui = true;
not_null(plugin.getControl("mix")).not_on_gui = true;
not_null(plugin.getControl("volL")).not_on_gui = false;
not_null(plugin.getControl("panL")).not_on_gui = false;
not_null(plugin.getControl("volR")).not_on_gui = false;
not_null(plugin.getControl("panR")).not_on_gui = false;
break;
}
// xxx
}
if (plugin) { if (plugin) {
result.push( result.push(
<tr> <tr>
@@ -200,29 +241,33 @@ export const MidiBindingDialog =
</td> </td>
</tr> </tr>
); );
result.push( if (!isSplit)
<tr> {
<td className={classes.nameTd}> result.push(
<Typography noWrap style={{ verticalAlign: "center", height: 48 }}> <tr>
Bypass <td className={classes.nameTd}>
</Typography> <Typography noWrap style={{ verticalAlign: "center", height: 48 }}>
</td> Bypass
<td className={classes.bindingTd}> </Typography>
<MidiBindingView instanceId={item.instanceId} midiBinding={item.getMidiBinding("__bypass")} </td>
onListen={(instanceId: number, symbol: string, listenForControl: boolean) => { <td className={classes.bindingTd}>
if (instanceId === -2) <MidiBindingView instanceId={item.instanceId} midiBinding={item.getMidiBinding("__bypass")}
{ uiPlugin={plugin}
this.cancelListenForControl(); onListen={(instanceId: number, symbol: string, listenForControl: boolean) => {
} else { if (instanceId === -2)
this.handleListenForControl(instanceId, symbol, listenForControl); {
} this.cancelListenForControl();
}} } else {
listen={item.instanceId === this.state.listenInstanceId && this.state.listenSymbol === "__bypass"} this.handleListenForControl(instanceId, symbol, listenForControl);
onChange={(instanceId: number, newItem: MidiBinding) => this.handleItemChanged(instanceId, newItem)} }
/> }}
</td> listen={item.instanceId === this.state.listenInstanceId && this.state.listenSymbol === "__bypass"}
</tr> onChange={(instanceId: number, newItem: MidiBinding) => this.handleItemChanged(instanceId, newItem)}
); />
</td>
</tr>
);
}
for (let i = 0; i < plugin.controls.length; ++i) { for (let i = 0; i < plugin.controls.length; ++i) {
let control = plugin.controls[i]; let control = plugin.controls[i];
@@ -244,6 +289,7 @@ export const MidiBindingDialog =
</td> </td>
<td className={classes.bindingTd}> <td className={classes.bindingTd}>
<MidiBindingView instanceId={item.instanceId} <MidiBindingView instanceId={item.instanceId}
uiPlugin={plugin}
midiBinding={item.getMidiBinding(symbol)} midiBinding={item.getMidiBinding(symbol)}
listen={item.instanceId === this.state.listenInstanceId && this.state.listenSymbol === symbol} listen={item.instanceId === this.state.listenInstanceId && this.state.listenSymbol === symbol}
onListen={(instanceId: number, symbol: string, listenForControl: boolean) => { onListen={(instanceId: number, symbol: string, listenForControl: boolean) => {
+13 -10
View File
@@ -367,12 +367,14 @@ const PedalboardView =
frameRef: React.RefObject<HTMLDivElement>; frameRef: React.RefObject<HTMLDivElement>;
scrollRef: React.RefObject<HTMLDivElement>; scrollRef: React.RefObject<HTMLDivElement>;
private bgColor: string;
constructor(props: PedalboardProps) { constructor(props: PedalboardProps) {
super(props); super(props);
this.model = PiPedalModelFactory.getInstance(); this.model = PiPedalModelFactory.getInstance();
this.bgColor = props.theme.palette.background.default
if (!props.selectedId) props.selectedId = -1; if (!props.selectedId) props.selectedId = -1;
this.state = { this.state = {
pedalboard: this.model.pedalboard.get(), pedalboard: this.model.pedalboard.get(),
@@ -692,6 +694,7 @@ const PedalboardView =
let y_ = item.bounds.y + CELL_HEIGHT / 2; let y_ = item.bounds.y + CELL_HEIGHT / 2;
let numberOfOutputs = item.numberOfOutputs; let numberOfOutputs = item.numberOfOutputs;
let color = enabled ? ENABLED_CONNECTOR_COLOR : DISABLED_CONNECTOR_COLOR; let color = enabled ? ENABLED_CONNECTOR_COLOR : DISABLED_CONNECTOR_COLOR;
let stereoCenterColor = this.bgColor;
let svgPath = new SvgPathBuilder().moveTo(x_, y_).lineTo(x_ + CELL_WIDTH, y_).toString(); let svgPath = new SvgPathBuilder().moveTo(x_, y_).lineTo(x_ + CELL_WIDTH, y_).toString();
if (numberOfOutputs === 2) { if (numberOfOutputs === 2) {
@@ -699,7 +702,7 @@ const PedalboardView =
<path key={this.renderKey++} d={svgPath} stroke={color} strokeWidth={SVG_STEREO_STROKE_WIDTH} /> <path key={this.renderKey++} d={svgPath} stroke={color} strokeWidth={SVG_STEREO_STROKE_WIDTH} />
)); ));
output.push(( output.push((
<path key={this.renderKey++} d={svgPath} stroke="white" strokeWidth={SVG_STROKE_WIDTH} /> <path key={this.renderKey++} d={svgPath} stroke={stereoCenterColor} strokeWidth={SVG_STROKE_WIDTH} />
)); ));
} else if (numberOfOutputs === 1) { } else if (numberOfOutputs === 1) {
output.push(( output.push((
@@ -727,14 +730,14 @@ const PedalboardView =
if (item.numberOfInputs === 2 && item.topChildren[0].numberOfInputs === 2) { if (item.numberOfInputs === 2 && item.topChildren[0].numberOfInputs === 2) {
output.push((<path key={this.renderKey++} d={topStartPath} stroke={topColor} strokeWidth={SVG_STEREO_STROKE_WIDTH} />)); output.push((<path key={this.renderKey++} d={topStartPath} stroke={topColor} strokeWidth={SVG_STEREO_STROKE_WIDTH} />));
output.push((<path key={this.renderKey++} d={topStartPath} stroke="white" strokeWidth={SVG_STROKE_WIDTH} />)); output.push((<path key={this.renderKey++} d={topStartPath} stroke={this.bgColor} strokeWidth={SVG_STROKE_WIDTH} />));
} else if (item.numberOfInputs !== 0 && item.topChildren[0].numberOfInputs !== 0) { } else if (item.numberOfInputs !== 0 && item.topChildren[0].numberOfInputs !== 0) {
output.push((<path key={this.renderKey++} d={topStartPath} stroke={topColor} strokeWidth={SVG_STROKE_WIDTH} />)); output.push((<path key={this.renderKey++} d={topStartPath} stroke={topColor} strokeWidth={SVG_STROKE_WIDTH} />));
} }
if (item.numberOfInputs === 2 && item.bottomChildren[0].numberOfInputs === 2) { if (item.numberOfInputs === 2 && item.bottomChildren[0].numberOfInputs === 2) {
output.push((<path key={this.renderKey++} d={bottomStartPath} stroke={bottomColor} strokeWidth={SVG_STEREO_STROKE_WIDTH} />)); output.push((<path key={this.renderKey++} d={bottomStartPath} stroke={bottomColor} strokeWidth={SVG_STEREO_STROKE_WIDTH} />));
output.push((<path key={this.renderKey++} d={bottomStartPath} stroke="white" strokeWidth={SVG_STROKE_WIDTH} />)); output.push((<path key={this.renderKey++} d={bottomStartPath} stroke={this.bgColor} strokeWidth={SVG_STROKE_WIDTH} />));
} else if (item.numberOfInputs !== 0 && item.bottomChildren[0].numberOfInputs !== 0) { } else if (item.numberOfInputs !== 0 && item.bottomChildren[0].numberOfInputs !== 0) {
output.push((<path key={this.renderKey++} d={bottomStartPath} stroke={bottomColor} strokeWidth={SVG_STROKE_WIDTH} />)); output.push((<path key={this.renderKey++} d={bottomStartPath} stroke={bottomColor} strokeWidth={SVG_STROKE_WIDTH} />));
@@ -824,7 +827,7 @@ const PedalboardView =
<path key={this.renderKey++} d={firstPath} stroke={firstPathColor} strokeWidth={SVG_STEREO_STROKE_WIDTH} /> <path key={this.renderKey++} d={firstPath} stroke={firstPathColor} strokeWidth={SVG_STEREO_STROKE_WIDTH} />
)); ));
output.push(( output.push((
<path key={this.renderKey++} d={firstPath} stroke="white" strokeWidth={SVG_STROKE_WIDTH} /> <path key={this.renderKey++} d={firstPath} stroke={this.bgColor} strokeWidth={SVG_STROKE_WIDTH} />
)); ));
} else if (!firstPathAbsent) { } else if (!firstPathAbsent) {
output.push(( output.push((
@@ -836,7 +839,7 @@ const PedalboardView =
<path key={this.renderKey++} d={secondPath} stroke={secondPathColor} strokeWidth={SVG_STEREO_STROKE_WIDTH} /> <path key={this.renderKey++} d={secondPath} stroke={secondPathColor} strokeWidth={SVG_STEREO_STROKE_WIDTH} />
)); ));
output.push(( output.push((
<path key={this.renderKey++} d={secondPath} stroke="white" strokeWidth={SVG_STROKE_WIDTH} /> <path key={this.renderKey++} d={secondPath} stroke={this.bgColor} strokeWidth={SVG_STROKE_WIDTH} />
)); ));
} else if (!secondPathAbsent) { } else if (!secondPathAbsent) {
output.push(( output.push((
@@ -868,12 +871,12 @@ const PedalboardView =
// draw stereo inner lines. // draw stereo inner lines.
if (firstPathStereo) { if (firstPathStereo) {
output.push(( output.push((
<path key={this.renderKey++} d={firstPath} stroke="white" strokeWidth={SVG_STROKE_WIDTH} /> <path key={this.renderKey++} d={firstPath} stroke={this.bgColor} strokeWidth={SVG_STROKE_WIDTH} />
)); ));
} }
if (secondPathStereo) { if (secondPathStereo) {
output.push(( output.push((
<path key={this.renderKey++} d={secondPath} stroke="white" strokeWidth={SVG_STROKE_WIDTH} /> <path key={this.renderKey++} d={secondPath} stroke={this.bgColor} strokeWidth={SVG_STROKE_WIDTH} />
)); ));
} }
@@ -885,7 +888,7 @@ const PedalboardView =
<path key={this.renderKey++} d={thirdPath} stroke={secondPathColor} strokeWidth={SVG_STEREO_STROKE_WIDTH} /> <path key={this.renderKey++} d={thirdPath} stroke={secondPathColor} strokeWidth={SVG_STEREO_STROKE_WIDTH} />
)); ));
output.push(( output.push((
<path key={this.renderKey++} d={thirdPath} stroke="white" strokeWidth={SVG_STROKE_WIDTH} /> <path key={this.renderKey++} d={thirdPath} stroke={this.bgColor} strokeWidth={SVG_STROKE_WIDTH} />
)); ));
@@ -958,7 +961,7 @@ const PedalboardView =
let outputs: ReactNode[] = []; let outputs: ReactNode[] = [];
this.renderConnectors(outputs, layoutChain, true, false); this.renderConnectors(outputs, layoutChain, true, false);
return ( return (
<div style={{width: layoutSize.width, height: layoutSize.height, overflow: "hidden"}}> <div key="connectors" style={{width: layoutSize.width, height: layoutSize.height, overflow: "hidden"}}>
<svg width={layoutSize.width} height={layoutSize.height} <svg width={layoutSize.width} height={layoutSize.height}
xmlns="http://www.w3.org/2000/svg" viewBox={"0 0 " + layoutSize.width + " " + layoutSize.height}> xmlns="http://www.w3.org/2000/svg" viewBox={"0 0 " + layoutSize.width + " " + layoutSize.height}>
<g fill="none"> <g fill="none">
+1
View File
@@ -423,6 +423,7 @@ export class PiPedalModel //implements PiPedalModel
//if (retry !== 0) { //if (retry !== 0) {
if (this.restartExpected) { if (this.restartExpected) {
this.setState(State.ApplyingChanges); this.setState(State.ApplyingChanges);
this.restartExpected = false;
} else { } else {
this.setState(State.Reconnecting); this.setState(State.Reconnecting);
} }
+57 -42
View File
@@ -25,12 +25,10 @@ import createStyles from '@mui/styles/createStyles';
import withStyles from '@mui/styles/withStyles'; import withStyles from '@mui/styles/withStyles';
import IControlViewFactory from './IControlViewFactory'; import IControlViewFactory from './IControlViewFactory';
import { PiPedalModelFactory, PiPedalModel,MonitorPortHandle } from "./PiPedalModel"; import { PiPedalModelFactory, PiPedalModel, MonitorPortHandle } from "./PiPedalModel";
import { PedalboardItem } from './Pedalboard'; import { PedalboardItem } from './Pedalboard';
import PluginControlView, { ControlGroup,ControlViewCustomization } from './PluginControlView'; import PluginControlView, { ControlGroup, ControlViewCustomization } from './PluginControlView';
import ToobFrequencyResponseView from './ToobFrequencyResponseView'; // import ToobFrequencyResponseView from './ToobFrequencyResponseView';
const styles = (theme: Theme) => createStyles({ const styles = (theme: Theme) => createStyles({
}); });
@@ -46,13 +44,12 @@ interface ToobMLState {
const ToobMLView = const ToobMLView =
withStyles(styles, { withTheme: true })( withStyles(styles, { withTheme: true })(
class extends React.Component<ToobMLProps, ToobMLState> class extends React.Component<ToobMLProps, ToobMLState>
implements ControlViewCustomization implements ControlViewCustomization {
{
model: PiPedalModel; model: PiPedalModel;
gainRef: React.RefObject<HTMLDivElement>; gainRef: React.RefObject<HTMLDivElement>;
customizationId: number = 1; customizationId: number = 1;
constructor(props: ToobMLProps) { constructor(props: ToobMLProps) {
super(props); super(props);
@@ -65,57 +62,75 @@ const ToobMLView =
subscribedId?: number = undefined; subscribedId?: number = undefined;
monitorPortHandle?: MonitorPortHandle = undefined; monitorPortHandle?: MonitorPortHandle = undefined;
removeGainEnabledSubscription() removeGainEnabledSubscription() {
{ if (this.monitorPortHandle) {
if (this.monitorPortHandle)
{
this.model.unmonitorPort(this.monitorPortHandle); this.model.unmonitorPort(this.monitorPortHandle);
this.monitorPortHandle = undefined; this.monitorPortHandle = undefined;
} }
} }
addGainEnabledSubscription(instanceId: number) addGainEnabledSubscription(instanceId: number) {
{
this.removeGainEnabledSubscription(); this.removeGainEnabledSubscription();
this.subscribedId = instanceId; this.subscribedId = instanceId;
this.monitorPortHandle = this.model.monitorPort(instanceId,"gainEnable",0.1, this.monitorPortHandle = this.model.monitorPort(instanceId, "gainEnable", 0.1,
(value: number) => { (value: number) => {
if (this.gainRef.current) if (this.gainRef.current) {
{ this.gainRef.current.style.opacity = value !== 0.0 ? "1.0" : "0.4";
this.gainRef.current.style.opacity = value !== 0.0? "1.0": "0.4"; if (value !== 0.0) {
this.gainRef.current.style.pointerEvents = 'auto';
} else {
this.gainRef.current.style.pointerEvents = 'none';
}
} }
this.setState({gainEnabled: value !== 0.0 }); this.setState({ gainEnabled: value !== 0.0 });
}); });
} }
componentDidUpdate() // componentDidUpdate() {
{ // if (this.props.instanceId !== this.subscribedId) {
if (this.props.instanceId !== this.subscribedId) // this.removeGainEnabledSubscription();
{ // this.addGainEnabledSubscription(this.props.instanceId);
this.removeGainEnabledSubscription();
this.addGainEnabledSubscription(this.props.instanceId);
// }
// }
componentDidMount() {
this.addGainEnabledSubscription(this.props.instanceId);
if (this.gainRef.current) {
this.gainRef.current.style.opacity = '0.4';
this.gainRef.current.style.pointerEvents = 'none';
} }
} }
componentDidMount() componentWillUnmount() {
{
this.addGainEnabledSubscription(this.props.instanceId);
}
componentWillUnmount()
{
this.removeGainEnabledSubscription(); this.removeGainEnabledSubscription();
} }
ModifyControls(controls: (React.ReactNode| ControlGroup)[]): (React.ReactNode| ControlGroup)[] ModifyControls(controls: (React.ReactNode | ControlGroup)[]): (React.ReactNode | ControlGroup)[] {
{ // Find EQ group
let group = controls[4] as ControlGroup; // let group = controls.find((control) => typeof control !== 'string' && (control as ControlGroup).name === "EQ") as ControlGroup;
group.controls.splice(0,0, // if (group) {
( <ToobFrequencyResponseView instanceId={this.props.instanceId} />) // group.controls.splice(0,0,
); // ( <ToobFrequencyResponseView
// instanceId={this.props.instanceId}
let gainControl: React.ReactElement = controls[2] as React.ReactElement; // />)
if (gainControl) // );
// }
let controlIndex: number = -1;
let controlValues = this.props.item.controlValues
for (let i:number = 0; i < controlValues.length; ++i)
{ {
controls[2] = (<div ref={this.gainRef}> { gainControl} </div>); let ctl: any = controls[i];
let key: any = ctl?.props?.uiControl?.symbol??"";
if (key === "gain")
{
controlIndex = i;
break;
}
}
if (controlIndex !== -1 ) {
let gainControl: React.ReactElement = controls[controlIndex] as React.ReactElement;
controls[controlIndex] = (<div ref={this.gainRef}> {gainControl} </div>);
} }
return controls; return controls;
} }
+105 -2
View File
@@ -37,6 +37,7 @@
#include <random> #include <random>
#include "AudioConfig.hpp" #include "AudioConfig.hpp"
#include "WifiChannelSelectors.hpp" #include "WifiChannelSelectors.hpp"
#include <grp.h>
#if JACK_HOST #if JACK_HOST
#define INSTALL_JACK_SERVICE 1 #define INSTALL_JACK_SERVICE 1
@@ -592,6 +593,100 @@ static std::string RandomChars(int nchars)
return s.str(); return s.str();
} }
void SetVarPermissions(
const std::filesystem::path &path,
std::filesystem::perms directoryPermissions,
std::filesystem::perms filePermissions,
uid_t uid, gid_t gid)
{
namespace fs = std::filesystem;
using namespace std::filesystem;
try {
if (fs::exists(path)) {
if (fs::is_directory(path)) {
fs::permissions(path, directoryPermissions, fs::perm_options::replace);
for (const auto& entry : fs::recursive_directory_iterator(path)) {
try {
if (fs::is_directory(entry))
{
fs::permissions(entry.path(), directoryPermissions, fs::perm_options::replace);
} else {
fs::permissions(entry.path(),filePermissions, fs::perm_options::replace);
}
} catch (const std::exception &e)
{
std::cout << "Error: failed to set permissions on file " << entry.path() << std::endl;
}
if (chown(path.c_str(),uid,gid) != 0)
{
std::cout << "Error: failed to set ownership of file " << entry.path() << std::endl;
}
}
} else {
fs::permissions(path,filePermissions, fs::perm_options::replace);
chown(path.c_str(),uid,gid);
}
} else {
std::cout << "Error: Path does not exist: " << path << std::endl;
}
} catch (const std::filesystem::filesystem_error& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
}
static void FixPermissions()
{
namespace fs = std::filesystem;
using namespace std::filesystem;
fs::create_directories("/var/pipedal");
fs::perms directoryPermissions =
fs::perms::owner_read | fs::perms::owner_write | fs::perms::owner_exec |
fs::perms::group_read | fs::perms::group_write | fs::perms::group_exec |
fs::perms::others_read | fs::perms::others_exec |
fs::perms::set_gid;
fs::perms filePermissions =
fs::perms::owner_read | fs::perms::owner_write |
fs::perms::group_read | fs::perms::group_write |
fs::perms::others_read ;
uid_t uid;
struct passwd *passwd;
if ((passwd = getpwnam("pipedal_d")) == nullptr)
{
cout << "Error: " << "User 'pipedal_d' does not exist." << endl;
return;
}
uid = passwd->pw_uid;
gid_t gid = passwd->pw_gid;
SetVarPermissions("/var/pipedal",directoryPermissions,filePermissions,uid,gid);
fs::perms wpa_supplicant_perms =
fs::perms::owner_read | fs::perms::owner_write |
fs::perms::group_read | fs::perms::group_write;
std::filesystem::path wpa_config_path{ "/etc/pipedal/config/wpa_supplicant/wpa_supplicant-pipedal.conf"};
try {
fs::permissions(wpa_config_path,wpa_supplicant_perms, fs::perm_options::replace);
struct group*grp = getgrnam("netdev");
if (grp != nullptr)
{
if (chown(wpa_config_path.c_str(),0,grp->gr_gid) != 0)
{
cout << "Error: Failed to change ownership of " << wpa_config_path << endl;
}
}
} catch (const std::exception&e)
{
cout << "Error: Failed to set permissions on " << wpa_config_path << ". " << e.what() << endl;
}
}
static void PrepareServiceConfigurationFile(uint16_t portNumber) static void PrepareServiceConfigurationFile(uint16_t portNumber)
{ {
ServiceConfiguration serviceConfiguration; ServiceConfiguration serviceConfiguration;
@@ -827,6 +922,7 @@ void Install(const std::filesystem::path &programPrefix, const std::string endpo
sysExec(SYSTEMCTL_BIN " daemon-reload"); sysExec(SYSTEMCTL_BIN " daemon-reload");
FixPermissions();
RestartService(false); RestartService(false);
EnableService(); EnableService();
} }
@@ -1070,6 +1166,7 @@ int main(int argc, char **argv)
bool enable_p2p = false, disable_p2p = false; bool enable_p2p = false, disable_p2p = false;
bool list_p2p_channels = false; bool list_p2p_channels = false;
bool get_current_port = false; bool get_current_port = false;
bool fix_permissions = false;
bool nosudo = false; bool nosudo = false;
bool excludeShutdownService = false; bool excludeShutdownService = false;
std::string prefixOption; std::string prefixOption;
@@ -1092,7 +1189,8 @@ int main(int argc, char **argv)
parser.AddOption("--enable-p2p", &enable_p2p); parser.AddOption("--enable-p2p", &enable_p2p);
parser.AddOption("--disable-p2p", &disable_p2p); parser.AddOption("--disable-p2p", &disable_p2p);
parser.AddOption("--list-p2p-channels", &list_p2p_channels); parser.AddOption("--list-p2p-channels", &list_p2p_channels);
parser.AddOption("--fix-permissions", &fix_permissions);
parser.AddOption("--get-current-port", &get_current_port); // private. For debug use only. parser.AddOption("--get-current-port", &get_current_port); // private. For debug use only.
@@ -1104,7 +1202,7 @@ int main(int argc, char **argv)
int actionCount = int actionCount =
help + get_current_port + install + uninstall + stop + start + enable + disable help + get_current_port + install + uninstall + stop + start + enable + disable
+ enable_ap + disable_ap + restart + enable_p2p + disable_p2p + enable_ap + disable_ap + restart + enable_p2p + disable_p2p
+ list_p2p_channels + list_p2p_channels + fix_permissions
; ;
if (actionCount > 1) if (actionCount > 1)
{ {
@@ -1169,6 +1267,11 @@ int main(int argc, char **argv)
try try
{ {
if (fix_permissions)
{
FixPermissions();
return EXIT_SUCCESS;
}
if (install) if (install)
{ {
std::filesystem::path prefix; std::filesystem::path prefix;
+9 -20
View File
@@ -261,15 +261,15 @@ void Lv2Pedalboard::PrepareMidiMap(const PedalboardItem &pedalboardItem)
{ {
if (pedalboardItem.midiBindings().size() != 0) if (pedalboardItem.midiBindings().size() != 0)
{ {
auto pluginInfo = pHost->GetPluginInfo(pedalboardItem.uri()); Lv2PluginInfo::ptr pluginInfo;
const Lv2PluginInfo *pPluginInfo; if (pedalboardItem.uri() == SPLIT_PEDALBOARD_ITEM_URI)
if (pluginInfo == nullptr && pedalboardItem.uri() == SPLIT_PEDALBOARD_ITEM_URI)
{ {
pPluginInfo = GetSplitterPluginInfo(); pluginInfo = GetSplitterPluginInfo();
} }
else else
{ {
pPluginInfo = pluginInfo.get(); pluginInfo = pHost->GetPluginInfo(pedalboardItem.uri());
} }
int effectIndex = this->GetIndexOfInstanceId(pedalboardItem.instanceId()); int effectIndex = this->GetIndexOfInstanceId(pedalboardItem.instanceId());
@@ -351,21 +351,10 @@ void Lv2Pedalboard::PrepareMidiMap(const Pedalboard &pedalboard)
auto &item = pedalboard.items()[i]; auto &item = pedalboard.items()[i];
PrepareMidiMap(item); PrepareMidiMap(item);
auto pluginInfo = pHost->GetPluginInfo(item.uri());
if (pluginInfo)
{
for (size_t bindingIndex = 0; bindingIndex < item.midiBindings().size(); ++bindingIndex)
{
auto &binding = item.midiBindings()[i];
{
}
}
}
std::sort(this->midiMappings.begin(), this->midiMappings.end(),
[](const MidiMapping &left, const MidiMapping &right)
{ return left.key < right.key; });
} }
std::sort(this->midiMappings.begin(), this->midiMappings.end(),
[](const MidiMapping &left, const MidiMapping &right)
{ return left.key < right.key; });
} }
void Lv2Pedalboard::Activate() void Lv2Pedalboard::Activate()
{ {
@@ -606,7 +595,7 @@ void Lv2Pedalboard::OnMidiMessage(size_t size, uint8_t *message,
if (size < 3) if (size < 3)
return; return;
index = message[1]; index = message[1];
value = 127; value = message[2] == 0? 0: 127; // zero velocity = note off.
} }
else if (cmd == 0xB0) // midi control. else if (cmd == 0xB0) // midi control.
{ {
+6 -8
View File
@@ -1266,21 +1266,20 @@ void PiPedalModel::OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, f
PedalboardItem *item = this->pedalboard.GetItem(instanceId); PedalboardItem *item = this->pedalboard.GetItem(instanceId);
if (item) if (item)
{ {
const Lv2PluginInfo *pPluginInfo; Lv2PluginInfo::ptr pPluginInfo;
if (item->uri() == SPLIT_PEDALBOARD_ITEM_URI) if (item->uri() == SPLIT_PEDALBOARD_ITEM_URI)
{ {
pPluginInfo = GetSplitterPluginInfo(); pPluginInfo = GetSplitterPluginInfo();
} }
else else
{ {
auto pluginInfo = lv2Host.GetPluginInfo(item->uri()); pPluginInfo = lv2Host.GetPluginInfo(item->uri());
pPluginInfo = pluginInfo.get();
} }
if (pPluginInfo) if (pPluginInfo)
{ {
if (portIndex == -1) if (portIndex == -1)
{ {
// bypass! // bypass! yyy this->value!!!
this->pedalboard.SetItemEnabled(instanceId, value != 0); this->pedalboard.SetItemEnabled(instanceId, value != 0);
// take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us) // take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us)
IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()]; IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()];
@@ -1675,16 +1674,15 @@ void PiPedalModel::SetJackServerSettings(const JackServerSettings &jackServerSet
void PiPedalModel::UpdateDefaults(PedalboardItem *pedalboardItem) void PiPedalModel::UpdateDefaults(PedalboardItem *pedalboardItem)
{ {
std::shared_ptr<Lv2PluginInfo> t = lv2Host.GetPluginInfo(pedalboardItem->uri()); std::shared_ptr<Lv2PluginInfo> pPlugin = lv2Host.GetPluginInfo(pedalboardItem->uri());
const Lv2PluginInfo *pPlugin = t.get(); if (!pPlugin)
if (pPlugin == nullptr)
{ {
if (pedalboardItem->uri() == SPLIT_PEDALBOARD_ITEM_URI) if (pedalboardItem->uri() == SPLIT_PEDALBOARD_ITEM_URI)
{ {
pPlugin = GetSplitterPluginInfo(); pPlugin = GetSplitterPluginInfo();
} }
} }
if (pPlugin != nullptr) if (pPlugin)
{ {
for (size_t i = 0; i < pPlugin->ports().size(); ++i) for (size_t i = 0; i < pPlugin->ports().size(); ++i)
{ {
+4
View File
@@ -1353,6 +1353,10 @@ Lv2PortGroup::Lv2PortGroup(PluginHost *lv2Host, const std::string &groupUri)
name_ = nodeAsString(nameNode); name_ = nodeAsString(nameNode);
} }
bool Lv2PluginInfo::isSplit() const
{
return uri_ == SPLIT_PEDALBOARD_ITEM_URI;
}
std::shared_ptr<HostWorkerThread> PluginHost::GetHostWorkerThread() std::shared_ptr<HostWorkerThread> PluginHost::GetHostWorkerThread()
{ {
return pHostWorkerThread; return pHostWorkerThread;
+2
View File
@@ -337,8 +337,10 @@ namespace pipedal
friend class PluginHost; friend class PluginHost;
public: public:
using ptr = std::shared_ptr<Lv2PluginInfo>;
Lv2PluginInfo(PluginHost *lv2Host, LilvWorld *pWorld, const LilvPlugin *); Lv2PluginInfo(PluginHost *lv2Host, LilvWorld *pWorld, const LilvPlugin *);
Lv2PluginInfo() {} Lv2PluginInfo() {}
bool isSplit() const ;
private: private:
std::shared_ptr<PiPedalUI> FindWritablePathProperties(PluginHost *lv2Host, const LilvPlugin *pPlugin); std::shared_ptr<PiPedalUI> FindWritablePathProperties(PluginHost *lv2Host, const LilvPlugin *pPlugin);
+4 -2
View File
@@ -33,6 +33,8 @@ static std::shared_ptr<Lv2PortInfo> MakeBypassPortInfo()
bypassPortInfo->is_input(true); bypassPortInfo->is_input(true);
bypassPortInfo->is_control_port(true); bypassPortInfo->is_control_port(true);
bypassPortInfo->index(-1); bypassPortInfo->index(-1);
bypassPortInfo->min_value(0); bypassPortInfo->min_value(0);
bypassPortInfo->max_value(1); bypassPortInfo->max_value(1);
bypassPortInfo->default_value(1); bypassPortInfo->default_value(1);
@@ -159,9 +161,9 @@ static Lv2PluginInfo makeSplitterPluginInfo()
return result; return result;
} }
Lv2PluginInfo g_splitterPluginInfo = makeSplitterPluginInfo(); Lv2PluginInfo::ptr g_splitterPluginInfo = std::make_shared<Lv2PluginInfo>(makeSplitterPluginInfo());
const Lv2PluginInfo *pipedal::GetSplitterPluginInfo() { return &g_splitterPluginInfo; } Lv2PluginInfo::ptr pipedal::GetSplitterPluginInfo() { return g_splitterPluginInfo; }
int SplitEffect::GetControlIndex(const std::string &symbol) const int SplitEffect::GetControlIndex(const std::string &symbol) const
{ {
+1 -1
View File
@@ -36,7 +36,7 @@ namespace pipedal
class Lv2PluginInfo; class Lv2PluginInfo;
class Lv2PortInfo; class Lv2PortInfo;
const Lv2PluginInfo *GetSplitterPluginInfo(); std::shared_ptr<Lv2PluginInfo> GetSplitterPluginInfo();
const Lv2PortInfo *GetBypassPortInfo(); const Lv2PortInfo *GetBypassPortInfo();
+10 -5
View File
@@ -1624,14 +1624,19 @@ std::string Storage::UploadUserFile(const std::string &directory, const std::str
throw std::logic_error("patchProperty directory not implemented."); throw std::logic_error("patchProperty directory not implemented.");
} }
{ {
std::filesystem::create_directories(path.parent_path()); try {
std::filesystem::create_directories(path.parent_path());
std::ofstream f(path, std::ios_base::trunc | std::ios_base::binary); std::ofstream f(path, std::ios_base::trunc | std::ios_base::binary);
if (!f.is_open()) if (!f.is_open())
{
throw std::logic_error(SS("Can't create file " << path << "."));
}
f.write(fileBody.c_str(), fileBody.length());
} catch (const std::exception &e)
{ {
throw std::logic_error(SS("Can't create file " << path << ".")); Lv2Log::error(SS("Upload failed. " << e.what()));
} }
f.write(fileBody.c_str(), fileBody.length());
} }
return path.string(); return path.string();
} }